package cn.itcast.client;

import cn.itcast.message.*;
import cn.itcast.protocol.MessageCodecSharable;
import cn.itcast.protocol.ProcotolFrameDecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
public class ChatClient {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        // 为了实现active方法new 出来的线程 和 channelRead方法的线程通信
        CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);
        // 客户端接收到服务器端的ChannelRead方法后 继续执行ChannelActive方法 定义一个变量来告诉ChannelActive方法 是否登录成功
        AtomicBoolean LOGIN = new AtomicBoolean(false);

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(group);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProcotolFrameDecoder());
//                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(MESSAGE_CODEC);
                    // 如果3s内没有向服务器写数据 则 IdleState.WRITE_IDLE 触发
                    ch.pipeline().addLast(new IdleStateHandler(0,3,0){
                        @Override
                        protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
                            if(evt.state() == IdleState.WRITER_IDLE){
                                log.debug("已经3s 没有写到数据了,发送一个心跳包");
                                ctx.writeAndFlush(new PingMessage());

                            }
                        }
                    });
                    // ChannelDuplexHandler 同时具有入站 出站 处理器
//                    ch.pipeline().addLast(new ChannelDuplexHandler(){
//                        // 用来触发特殊事件
//                        @Override
//                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
//                            IdleStateEvent event = (IdleStateEvent)evt;
//                            if(event.state() == IdleState.WRITER_IDLE){
//                                log.debug("已经3s 没有写到数据了,发送一个心跳包");
//                                ctx.writeAndFlush(new PingMessage());
//
//                            }
//                        }
//                    });
                    // 方法二 在这里自定义 handler 然后通过 重写 active方法
                    ch.pipeline().addLast("client handler", new ChannelInboundHandlerAdapter(){
                        // 在连接建立后触发
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            // 新线程负责接收用户在控制台的输入,负责向服务器发送消息
                            new Thread(()->{
                                Scanner scanner = new Scanner(System.in);
                                System.out.println("请输入用户名：");
                                String username = scanner.nextLine();
                                System.out.println("请输入密码：");
                                String password = scanner.nextLine();
                                // 构造消息对象
                                LoginRequestMessage message = new LoginRequestMessage(username, password);
                                // 发送消息
                                ctx.writeAndFlush(message);
                                System.out.println("等待输入");
                                try {
                                    WAIT_FOR_LOGIN.await();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                // 检验登录是否成功
                                if (!LOGIN.get()) {
                                    ctx.channel().close();
                                    return;
                                }
                                while(true){
                                    System.out.println("========================");
                                    System.out.println("send [username] [content]");
                                    System.out.println("gsend [group] [content]");
                                    System.out.println("gcreate [group name] [m1,m2,m3]");
                                    System.out.println("gmembers [group name]");
                                    System.out.println("gjoin [group name]");
                                    System.out.println("gquip [group name]");
                                    System.out.println("quit");
                                    System.out.println("========================");

                                    String command = scanner.nextLine();
                                    String[] s = command.split(" ");
                                    switch (s[0]){
                                        case "send":
                                            ctx.writeAndFlush(new ChatRequestMessage(username,s[1],s[2]));
                                            break;
                                        case "gsend":
                                            ctx.writeAndFlush(new GroupChatRequestMessage(username,s[1],s[2]));
                                            break;
                                        case "gcreate":
                                            Set<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));
                                            set.add(username);
                                            ctx.writeAndFlush(new GroupCreateRequestMessage(s[1],set));
                                            break;
                                        case "gmembers":
                                            ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));
                                            break;
                                        case "gjoin":
                                            ctx.writeAndFlush(new GroupJoinRequestMessage(username,s[1]));
                                            break;
                                        case "gquip":
                                            ctx.writeAndFlush(new GroupQuitRequestMessage(username,s[1]));
                                            break;
                                        case "quit":
                                            ctx.channel().close();
                                            return;

                                    }
                                }
                            },"system in").start();
                        }
                        // 接收响应消息
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            log.debug("msg:{}",msg);
                            if ((msg instanceof LoginResponseMessage)) {
                                LoginResponseMessage response = (LoginResponseMessage) msg;
                                if (response.isSuccess()) {
                                    LOGIN.set(true);
                                }
                            }
                            // 唤醒登录线程
                            WAIT_FOR_LOGIN.countDown();
                        }

                        @Override
                        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                            super.channelInactive(ctx);
                        }

                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                            super.exceptionCaught(ctx, cause);
                        }
                    });
                }
            });
            Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
            // 连接建立后 有两种方式 继续往下走
            // 这里1 sync方法会阻塞 直到连接建立成功 所以可以写在这里
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.error("client error", e);
        } finally {
            group.shutdownGracefully();
        }
    }
}
